import copy
import os
import ujson

from .u_msc import is_str_numerical
from .u_crypto import UCrypto
from .common_func.file_open import FileOpen


class UJson:
    @classmethod
    def load_json_file(cls, file_path: str, encoding="utf-8") -> (dict, str):
        """
        从json文件加载字典
        :param file_path:
        :param encoding:
        :return: (加载结果，错误信息)
        """
        res = dict()
        err = ""
        try:
            with FileOpen(file_path, 'rt', encoding=encoding) as f:
                res = ujson.load(f)
        except Exception as e:
            err = str(e) + f", error line:{e.__traceback__.tb_lineno}"

        return res, err

    @classmethod
    def load_json_str(cls, json_str: str) -> (dict, str):
        """
        从json文件加载字典
        :param json_str:
        :return: (加载结果，错误信息)
        """
        res = dict()
        err = ""
        try:
            res = ujson.loads(json_str)
        except Exception as e:
            err = str(e) + f", error line:{e.__traceback__.tb_lineno}"

        return res, err

    @staticmethod
    def dump_file(json_dict, file_path, encoding='utf-8', ensure_ascii=False, indent=4, encrypt_mode=""):
        with FileOpen(file_path, "wt", encoding=encoding, encrypt_mode=encrypt_mode) as f:
            ujson.dump(json_dict, f, ensure_ascii=ensure_ascii, indent=indent)

    @staticmethod
    def dumps(json_dict, ensure_ascii=False, indent=4):
        return ujson.dumps(json_dict, ensure_ascii=ensure_ascii, indent=indent)

    @classmethod
    def loads(cls, string):
        result = string
        while not cls.is_json_format(result):
            result = ujson.loads(result)
        return result

    @classmethod
    def load(cls, string, replace):
        """
        将null替换成replace
        :param string:
        :param replace:
        :return:
        """
        result = cls.loads(string)
        for key, value in result.items():
            if value is None:
                result[key] = replace
        return result

    @staticmethod
    def rm_none_value(json_dict: dict):
        """
        去除字典中值为None的项
        :param json_dict:
        :return:
        """
        t = copy.deepcopy(json_dict)
        for key, value in json_dict.items():
            if value is None:
                t.pop(key)
        return t

    @staticmethod
    def fmt_str_value_to_int(json_dict: dict) -> dict:
        """
        将字典中的字符串值尝试转换为int，失败则保持原值, 通过深拷贝，保持入参字典不变化
        :param json_dict:
        :return:
        """
        if not isinstance(json_dict, dict):
            return dict()
        res = copy.deepcopy(json_dict)
        for k, v in json_dict.items():
            if isinstance(v, str) and v.isdigit():
                res[k] = int(v)

        return res

    @classmethod
    def field_encrypt(cls, json_dict: dict, field_name: str, cipher_encoding="base64", encrypt_mode="DES"):
        """
        遍历字典， 将所有key为 field_name 的值按要求加密，返回加密后的字典
        :param json_dict:
        :param field_name
        :param cipher_encoding 密文编码格式
        :param encrypt_mode 加密模式，支持AES和DES
        :return:
        """
        if not isinstance(json_dict, dict):
            return json_dict

        copy_json = copy.deepcopy(json_dict)
        for key, value in json_dict.items():
            if key == field_name:
                if isinstance(value, dict) or isinstance(value, list):
                    str_value = ujson.dumps(value)
                else:
                    str_value = str(value)
                encrypt_value = UCrypto.encrypt(str_value, cipher_encoding=cipher_encoding, encrypt_mode=encrypt_mode).decode()
                copy_json[key] = encrypt_value
                continue
            if isinstance(value, dict):
                copy_json[key] = cls.field_encrypt(value, field_name, cipher_encoding, encrypt_mode)
            elif isinstance(value, list):
                encrypt_value = list()
                for list_value in value:
                    encrypt_value.append(cls.field_encrypt(list_value, field_name, cipher_encoding, encrypt_mode))
                copy_json[key] = encrypt_value
        return copy_json

    @classmethod
    def field_decrypt(cls, json_dict: dict, field_name: str, cipher_encoding="base64", encrypt_mode="DES"):
        """
        遍历字典， 将所有key为 field_name 的值按要求解密，返回解密后的字典
        :param json_dict:
        :param field_name
        :param cipher_encoding 密文编码格式
        :param encrypt_mode 加密模式，支持AES和DES
        :return:
        """
        if not isinstance(json_dict, dict):
            return json_dict

        copy_json = copy.deepcopy(json_dict)
        for key, value in json_dict.items():
            if key == field_name:
                if isinstance(value, str) or isinstance(value, bytes):
                    decrypt_value = UCrypto.decrypt(value, cipher_encoding=cipher_encoding, encrypt_mode=encrypt_mode)
                    copy_json[key] = decrypt_value
                continue
            if isinstance(value, dict):
                copy_json[key] = cls.field_decrypt(value, field_name, cipher_encoding, encrypt_mode)
            elif isinstance(value, list):
                decrypt_value = list()
                for list_value in value:
                    decrypt_value.append(cls.field_decrypt(list_value, field_name, cipher_encoding, encrypt_mode))
                copy_json[key] = decrypt_value
        return copy_json

    @classmethod
    def has_dict_value_blank(cls, dic, array):
        """
        检查字典中,键值为array数组中一员的值是否为空，如果有的话就true
        :param dic:
        :param array:
        :return:
        """
        for key in array:
            if key in dic:
                value = dic[key]
                if ((isinstance(value, str) and str(value).strip() == "") or
                        (isinstance(value, list) and cls.list_is_blank(value)) or
                        (isinstance(value, object) and value is None)):
                    return True
            else:
                return True
        return False

    @staticmethod
    def list_is_blank(i_list):
        """
        判断LIST是否为NULL
        :param i_list:
        :return:
        """
        for item in i_list:
            if (isinstance(item, str) and str(item).strip() == "") or (isinstance(item, object) and item is None):
                return True
        return False

    @staticmethod
    def dict_field_auto_add(dic, key, num=None):
        """
        num为空的时候dict字段默认+1
        :param num:
        :param dic:
        :param key:
        :return:
        """
        step = num or 1

        try:
            origin_value = dic[key]
        except KeyError:
            origin_value = 0
        if isinstance(origin_value, int) or isinstance(origin_value, float):
            dst_value = origin_value + step
        elif isinstance(origin_value, str):
            origin_value = is_str_numerical(origin_value)
            if origin_value:
                dst_value = origin_value + step
            else:
                return
        else:
            return

        dic[key] = dst_value

    @staticmethod
    def is_json_format(json_obj):
        return (isinstance(json_obj, dict) or isinstance(json_obj, list) or
                isinstance(json_obj, tuple) or isinstance(json_obj, set))

    @staticmethod
    def is_serial_format(json_obj):
        return isinstance(json_obj, set) or isinstance(json_obj, list) or isinstance(json_obj, tuple)

    @classmethod
    def path_traverse(cls, json_obj, father_path: str = "$"):
        path = dict()
        if isinstance(json_obj, dict):
            for k, v in json_obj.items():
                path[f"{father_path}['{k}']"] = v
        elif cls.is_serial_format(json_obj):
            for i in range(len(json_obj)):
                path[f"{father_path}[{i}]"] = json_obj[i]
        else:
            return path

        # if cur_depth < max_depth:
        sub_path = dict()
        for father_path, obj in path.items():
            if cls.is_json_format(obj):
                sub_path.update(cls.path_traverse(obj, father_path))
        if sub_path:
            path.update(sub_path)
        return path

    @staticmethod
    def _path_join(*_path, formatter: str = ""):
        """
        替代os.path.join
        :param _path:
        :param formatter 路径格式，默认为空，根据操作系统自适应，如果指定linux或windows， 则按照指定的操作系统路径格式执行
        :return:
        """
        if formatter.lower() == "windows":
            sep = "\\"
        elif formatter.lower() == "linux":
            sep = "/"
        else:
            return os.path.join(*_path)

        return_path = ""
        for s_path in _path:
            if s_path:
                return_path = return_path + s_path + sep
        return return_path[:-1]

    @classmethod
    def _extract_leaf_api_format(cls, obj, father_path: str = "", just_get_1_elem_for_serial = False):
        path = list()
        if isinstance(obj, dict):
            path = path + cls._extract_leaf_api_format_dict(obj, father_path, just_get_1_elem_for_serial)
        elif cls.is_serial_format(obj):
            path = path + cls._extract_leaf_api_format_serial(obj, father_path, just_get_1_elem_for_serial)
        return path

    @classmethod
    def _extract_leaf_api_format_dict(cls, obj, father_path: str = "", just_get_1_elem_for_serial=False):
        res = list()
        for k, v in obj.items():
            cur_path = cls._path_join(father_path, str(k), formatter="linux")
            if cls.is_json_format(v):
                res = res + cls._extract_leaf_api_format(v, cur_path, just_get_1_elem_for_serial)
            else:
                leaf_name = os.path.basename(cur_path)
                item = {"path": cur_path, "name": leaf_name, "value": [v]}
                res.append(item)
        return res

    @classmethod
    def _extract_leaf_api_format_serial(cls, obj, father_path: str = "", just_get_1_elem_for_serial=False):
        path = list()
        if not obj:
            return path
        obj_len = len(obj)
        if just_get_1_elem_for_serial:
            obj_len = 1
        for i in range(obj_len):
            cur_path = cls._path_join(father_path, str(i), formatter="linux")
            if cls.is_json_format(obj[i]):
                path = path + cls._extract_leaf_api_format(obj[i], cur_path, just_get_1_elem_for_serial)
            else:
                leaf_name = os.path.basename(cur_path)
                item = {"path": cur_path, "name": leaf_name, "value": [obj[i]]}
                path.append(item)
        return path

    @classmethod
    def _extract_leaf_json_path(cls, obj, father_path: str = "$", just_get_1_elem_for_serial = False):
        path = dict()
        if isinstance(obj, dict):
            path.update(cls._extract_leaf_json_path_dict(obj, father_path, just_get_1_elem_for_serial))
        elif cls.is_serial_format(obj):
            path.update(cls._extract_leaf_json_path_serial(obj, father_path, just_get_1_elem_for_serial))
        return path

    @classmethod
    def _extract_leaf_json_path_dict(cls, obj: dict, father_path: str = "$", just_get_1_elem_for_serial=False):
        path = dict()
        for k, v in obj.items():
            cur_path = f"{father_path}['{k}']"
            if cls.is_json_format(v):
                path.update(cls._extract_leaf_json_path(v, cur_path, just_get_1_elem_for_serial))
            else:
                path[cur_path] = v
        return path

    @classmethod
    def _extract_leaf_json_path_serial(cls, obj, father_path: str = "$", just_get_1_elem_for_serial=False):
        path = dict()
        if not obj:
            return path
        obj_len = len(obj)
        if just_get_1_elem_for_serial:
            obj_len = 1
        for i in range(obj_len):
            cur_path = f"{father_path}[{i}]"
            if cls.is_json_format(obj[i]):
                path.update(cls._extract_leaf_json_path(obj[i], cur_path, just_get_1_elem_for_serial))
            else:
                path[cur_path] = obj[i]
        return path

    @classmethod
    def extract_leaf(cls, obj, res_format: str = "json_path", just_get_1_elem_for_serial = False):
        if res_format == "api":
            return cls._extract_leaf_api_format(obj, "", just_get_1_elem_for_serial)
        else:
            return cls._extract_leaf_json_path(obj, "$", just_get_1_elem_for_serial)


    @staticmethod
    def _fix_end_flag(json_str):
        json_str = json_str.strip()
        element_map = {'{': '}', '[': ']'}
        element_end_set = {'}', ']'}
        element_start_set = set(element_map.keys())

        # 如果首字符不在start_list中，则是非法json，直接返回空字典字符串
        if json_str[0] not in (element_start_set | {'"'}):
            return "{}"
        if json_str[-1] == ",":
            json_str = json_str[:-1]
        replace_str = "_$$_"
        truncated_json = json_str.replace(" ", replace_str)

        add_list = list()
        for i in range(len(truncated_json)):
            if truncated_json[i] in element_start_set:
                add_list.insert(0, element_map.get(truncated_json[i]))
            elif truncated_json[i] in element_end_set and len(add_list):
                add_list.pop(0)

        for c in add_list:
            truncated_json = truncated_json + c

        return truncated_json.replace(replace_str, " ")

    @classmethod
    def auto_fix_loads(cls, json_str, cur_try: int = 0, max_try: int = 2):
        json_str = json_str.strip()
        if not json_str.endswith("}") and not json_str.endswith("]"):
            json_str = cls._truncate_to_lasted_end_flag(json_str)
            json_str = cls._fix_end_flag(json_str)
            cur_try += 1

        try:
            return UJson.loads(json_str)
        except ujson.JSONDecodeError as e:
            if cur_try >= max_try:
                print(e)
                return dict()
            e_str = str(e)
            if "string" in e_str and "Unmatched" in e_str:
                json_str = cls._fix_unmatched_quota(json_str)
            elif "escape" in e_str and "Unterminated" in e_str:
                json_str = cls._fix_unterminated_escape(json_str)
            elif "No" in e_str and ":" in e_str:
                json_str = json_str + ": null"

            json_str = cls._truncate_to_lasted_end_flag(json_str)
            fixed_str = cls._fix_end_flag(json_str)

            return cls.auto_fix_loads(fixed_str, cur_try + 1, max_try)

    @staticmethod
    def _truncate_to_lasted_end_flag(truncated_json):
        lasted_close_brace_pos = truncated_json.rfind("}")
        lasted_right_basket_pos = truncated_json.rfind("]")
        lasted_comma_pos = truncated_json.rfind(",")
        lasted_colon_pos = truncated_json.rfind(":")
        lasted_pos = max(lasted_colon_pos, lasted_close_brace_pos, lasted_comma_pos, lasted_right_basket_pos)
        if lasted_pos == lasted_colon_pos:
            return truncated_json[:lasted_pos] + ": null"
        elif lasted_pos == lasted_comma_pos:
            return truncated_json[:lasted_pos]
        else:
            return truncated_json[:lasted_pos + 1]


    @staticmethod
    def _fix_unterminated_escape(truncated_json):
        _pos = truncated_json.rfind("\\")
        return truncated_json[:_pos]

    @staticmethod
    def _fix_unmatched_quota(truncated_json):
        double_quotes_pos = truncated_json.rfind('"')
        truncated_json = truncated_json[:double_quotes_pos]
        if not truncated_json:
            return "{}"

        if truncated_json[-1] == "\\":
            truncated_json = truncated_json[:-1]

        return truncated_json
